<?php
namespace Pinenut\Service;
/**
 * 
 * Диспечер действий. Базовый вариант "в лоб".
 * 
 * @author kosh
 *
 */
class Dispatcher {
	
	static protected $contexts = array();
	
	/**
	 * Текущий контекст выполнения
	 * @var \Pinenut\Context
	 */
	static public $currentContext = null;
	
	static function dispatch(\Pinenut\Context $context) {
		
		// TODO: добавить контекст в пул, сделать его активным
		static::$currentContext = $context;
		
		$response = static::action($context->getMainRequest());
		
//		echo "<pre>";
//		print_r($context);
		
		return $response;
	}
	
	/**
	 * @param \Pinenut\Request $request
	 * @return \Pinenut\Response
	 */
	static function action($request) {
	
		$context = static::$currentContext;
	
		// добавляем в стек действие
		$context->pushRequest($request);
	
		// проверяем - может уже есть готовый ответ
		$response = $context->getResponse();
	
		if (! $response instanceof \Pinenut\Response) {
			// надо бы действие исполнить
	
			
			$response = static::doAction($request, $response);
						
	
			// добавляем ответ для текущего действия
			$context->addResponse($response);
	
		}
		// убираем из стека это действие
		$context->popRequest();
	
		return $response;
	}
	
	static protected function doAction($request, $response, $skipStages = 0) {
		
		$context = static::$currentContext;
		
		$actionClassName = static::getActionClassName($request);

		$stages = $actionClassName::stages($request);

		$action = static::getAction($actionClassName, $request);

		// нужно получить привилегии и проверить - есть ли у нас доступ

		$privileges = $action->privileges();

		if ($context->checkAccess($privileges)) {
			// доступ есть, делаем действие

			foreach ($stages as $stage) {
				if ($skipStages-- <= 0 ){
					// выполняем действие, точнее его этап
					$response = $action->doStage($stage, $response);
	
					// накатываем изменения данных каналов из действий,
					// которые были выполнены внутри только что отыгранного
	
					//if ($stagesCount > 2)
					$response = static::playChannels($response);
				}
			}
			//if ($stagesCount <= 2) $response = static::playChannels($response);

			// устанавливаем привилегии в ответ

		} else {
			// доступа нет, модифицируем ответ соответствующим образом
			$response = new \Application::$ResponseClass;
			$response->setException(new \Exception('Access denied', 403));
		}
		
		return $response;
			
	}
	
	/**
	 * Формируем имя класса действия, с проверками
	 * @param \Pinenut\Request $request
	 * @throws \Exception
	 * @return \Pinenut\Action
	 */
	static protected function getActionClassName($request) {
		// проверяем, что это запросы и что классы существуют и они - запросы
		if ($request instanceof  \Pinenut\Request) {
			$className = static::prepareClassName($request->action);
			if (!class_exists($className)) {
				throw new \Exception("Action '".htmlspecialchars($request->action).
									 "' ($className) not found!", 404);
			} else {
				return $className;
			}
		} else {
			throw new \Exception("Wrong request type", 500);
		}
	}
	
	/**
	 * Преобразование названия действия в имя класса
	 * По умолчанию используются namespace, и в нем класс, с заглавной буквы и постфиксом "Action.php"
	 * 
	 * @param string $actionName
	 * @return string
	 */
	static protected function prepareClassName($actionName) {
		// собираем имя класса
		$path = array_map('static::prepareClassNamePart', explode('.', $actionName));
		return implode('\\', $path).'Action';
	}
	
	static protected function prepareClassNamePart($part) {
		return ucfirst($part);
	}
	
	/**
	 * Создаем экземпляр действия, за одно проверяем корректность
	 * 
	 * @param string $className
	 * @param \Pinenut\Request $request
	 * @throws \Exception
	 * @return \Pinenut\Action
	 */
	static protected function getAction($className, \Pinenut\Request $request) {
		// TODO: проверить реакцию, если класс с неподходящим конструктором
		$action = new $className($request); 
		if ($action instanceof \Pinenut\Action) {
			return $action;
		} else {
			throw new \Exception("Action '".htmlspecialchars($request->action).
								 "' ($className) not support!", 500);
		}
	}
	
	static protected function playChannels($response) {
		// получаем список дочерних текущему запросу запросов
		$children = static::$currentContext->getChildrenResponses();
		$commands = array();
		foreach ($children as $childResponse) {
			// для каждого дочернего действия берём его готовый ответ и накатываем его на текущий
			$commands = array_merge($commands, $childResponse->getCommands());
		}
		$response->playCommands($commands);
		return $response;
	}
}